//Only for windows
#include <Windows.h>
#include <MMSystem.h>

// For all operating systems
#include <gl/GL.h>
#include <gl/GLU.h>
#include <gl/glut.h>

#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <sstream>

#include "imageloader.h"

using namespace std;

#define DEFAULT_WINDOW_WIDTH 640
#define DEFAULT_WINDOW_HEIGHT 480
#define KEYBOARD_S 115
#define KEYBOARD_ESC 27
#define KEYBOARD_F 102

int windowWidth = DEFAULT_WINDOW_WIDTH;
int windowHeight = DEFAULT_WINDOW_HEIGHT;

int fullscreen = 0;
int stereo = 0;

GLvoid InitGL(GLvoid);
GLvoid DrawGLScene(GLvoid);
GLvoid IdleGLScene(GLvoid);
GLvoid ReSizeGLScene(int width, int height);
GLvoid GLKeyDown(unsigned char key, int x, int y);
GLvoid PollJoyStick(GLvoid);
bool NeHeLoadBitmap(LPTSTR szFileName, GLuint &texid);
float yRotationAngle = 0.0f;

GLuint loadTexture(Image* image) {
	GLuint textureId;
	glGenTextures(1, &textureId); //Make room for our texture
	glBindTexture(GL_TEXTURE_2D, textureId); //Tell OpenGL which texture to edit
	//Map the image to the texture
	glTexImage2D(GL_TEXTURE_2D,                //Always GL_TEXTURE_2D
				 0,                            //0 for now
				 GL_RGB,                       //Format OpenGL uses for image
				 image->width, image->height,  //Width and height
				 0,                            //The border of the image
				 GL_RGB, //GL_RGB, because pixels are stored in RGB format
				 GL_UNSIGNED_BYTE, //GL_UNSIGNED_BYTE, because pixels are stored
				                   //as unsigned numbers
				 image->pixels);               //The actual pixel data
	return textureId; //Returns the id of the texture
}

GLuint _textureId; //The id of the texture
GLuint _textureId2;

int main(int argc, char* argv[]){
	glutInit(&argc, argv);
	/* setting up double buffering rbg and depth for this */
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(windowWidth, windowHeight);
	glutInitWindowPosition(100, 100);
	/* this name needs to match the name of the window in DrawGLScene */
	glutCreateWindow("My Glut Window");

	InitGL();

	/* the draw function will draw once and no more */
	glutDisplayFunc(DrawGLScene);
	/* this is the idle function it gets called as much as possible */
	//glutIdleFunc(DrawGLScene);
	glutIdleFunc(IdleGLScene);
	/* This is called everytime the window is altered */
	glutReshapeFunc(ReSizeGLScene);
	/* this gets called on a keyboard event */
	glutKeyboardFunc(GLKeyDown);

	/* Gets the loop rolling */
	glutMainLoop();

	return 0;
}

/* 
 * important GL initialization. You can mess with these,
 * but you don't need to for any of the projects
 */
GLvoid InitGL(){
	glShadeModel(GL_SMOOTH);							// Enable Smooth Shading
	glClearColor(0.5f, 0.5f, 0.5f, 0.5f);				// grey Background
	glClearDepth(1.0f);									// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_CULL_FACE);
	glShadeModel(GL_SMOOTH);
	glDepthFunc(GL_LEQUAL);								// The Type Of Depth Testing To Do
	glEnable ( GL_COLOR_MATERIAL );
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
}


/* insert your draw code in this function */
GLvoid DrawGLScene(){
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	/*
	 * if you are compiling on linux or mac remove all of the stereo code
	 */
	// This is the stereo code section don't touch this part until project 4 skip to the else statement
	if(stereo){
		HWND hwnd = FindWindow((LPCWSTR)"glut", (LPCWSTR)"My Glut Window");
		SetWindowPos(hwnd, NULL, 0, 0, GetSystemMetrics(SM_CXSCREEN) * 2, GetSystemMetrics(SM_CYSCREEN), CDS_FULLSCREEN);
		glViewport(0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));

		glTranslatef(0.0f, 0.0f, -5.0f);

		glColor3f(1.0f, 1.0f, 1.0f);
		glBegin(GL_TRIANGLES);
			glVertex3f(0.0f,0.0f,0.0f);
			glVertex3f(1.0f, 0.0f, 0.0f);
			glVertex3f(0.0f, 1.0f, 0.0f);
		glEnd();

		glViewport(GetSystemMetrics(SM_CXSCREEN), 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));

		glLoadIdentity();

		glTranslatef(0.0f, 0.0f, -5.0f);

		glColor3f(1.0f, 1.0f, 1.0f);
		glBegin(GL_TRIANGLES);
			glVertex3f(0.0f,0.0f,0.0f);
			glVertex3f(1.0f, 0.0f, 0.0f);
			glVertex3f(0.0f, 1.0f, 0.0f);
		glEnd();
	}
	//This is where you want to start editing the code to display what you are drawing. Currently it does one triangle. 
	else{
		/* This is your normal draw code. Put your code here. This should be kept no matter the OS you are using */

		//////////////////////////////Begin file parse code/////////////////////////////////////////
		std::vector<float> V;
		std::vector<float> VT;
		std::vector<float> VN;
		std::vector<int> F;
		std::vector<int> F1;
		std::vector<int> F2;
		std::ifstream File("tire.obj");
		string Line;
		string Name;
		while(std::getline(File, Line)){
			if(Line == "" || Line[0] == '#')// Skip everything and continue with the next line
				continue;

			std::istringstream iss (Line);
			iss >> Name;
			//save the Vertecies in a vector V
			if(Name == "v"){// Vertex
				float Vertex[3];
				sscanf(Line.c_str(), "%*s %f %f %f", &Vertex[0], &Vertex[1], &Vertex[2]);
				V.push_back(Vertex[0]);
				V.push_back(Vertex[1]);
				V.push_back(Vertex[2]);
				//printf("%f,%f,%f\n", Vertex[0], Vertex[1], Vertex[2]);
			}
			// Save Texture Mappings in vector VT
			if(Name == "vt"){// Texture Mapping
				float Vertex[2];
				sscanf(Line.c_str(), "%*s %f %f", &Vertex[0], &Vertex[1]);
				VT.push_back(Vertex[0]);
				VT.push_back(Vertex[1]);
				//printf("VT %f,%f\n", Vertex[0], Vertex[1]);
			}
			//Save Normals in vector VN
			if(Name == "vn"){// Normal
				float Vertex[3];
				sscanf(Line.c_str(), "%*s %f %f %f", &Vertex[0], &Vertex[1], &Vertex[2]);
				VN.push_back(Vertex[0]);
				VN.push_back(Vertex[1]);
				VN.push_back(Vertex[2]);
				//printf("%f,%f,%f\n", Vertex[0], Vertex[1], Vertex[2]);
			}
			/*
			for each set of mapping save as follows:
				*the first(vectors) element of each set goes into F
				*the second(Texture Mappings) element of each set goes into F1
				*the third(Normals) element of each set goes into F2
			*/
			if(Name == "f"){// Vertex
				int Vertex[9];
				sscanf(Line.c_str(), "%*s %d/%d/%d %d/%d/%d %d/%d/%d", &Vertex[0], &Vertex[1], &Vertex[2], &Vertex[3], &Vertex[4], &Vertex[5],&Vertex[6], &Vertex[7], &Vertex[8]);
				//push vertices
				F.push_back(Vertex[0]);
				F.push_back(Vertex[3]);
				F.push_back(Vertex[6]);
				//push texture mappings
				F1.push_back(Vertex[1]);
				F1.push_back(Vertex[4]);
				F1.push_back(Vertex[7]);
				//push normals
				F2.push_back(Vertex[2]);
				F2.push_back(Vertex[5]);
				F2.push_back(Vertex[8]);
				//printf("%f,%f,%f\n", Vertex[0], Vertex[1], Vertex[2]);
			}
		};
		File.close();
		////////////////////////////////////////////////////////////////////////////////////////////
		//Open Texture and load image
		Image* image2 = loadBMP("tire.bmp");
		_textureId2 = loadTexture(image2);
		delete image2;	//once the texture is created with opengl the image object is no longer needed

		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, _textureId2);
		//parameter presets from slides
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

		//  Box Presets
		glColor3f(1.0f,1.0f,1.0f);	// color white
		glTranslatef(-2.0f, -2.0f, -5.0f);
		glRotatef(yRotationAngle, 0.0f, 1.0f, 0.0f);


		//Draw Object
		glBegin(GL_TRIANGLES);
		//iterate through F handling each four elements as a set
		while(!F.empty())
		{
			//if F has less than 4 elements there has been an error or there are no more mappings
			if(F.size() < 4)
				break;
			//pull info out of F and F1 for 4 vertices and 4 Texture Mappings
			int p1,p2,p3,t1,t2,t3;
			p1 = (F[0]-1)*3; t1 = (F1[0]-1)*2;
			p2 = (F[1]-1)*3; t2 = (F1[1]-1)*2;
			p3 = (F[2]-1)*3; t3 = (F1[2]-1)*2;

			//draw set of four points and mappings
			glTexCoord2f(VT[t1],VT[t1+1]); glVertex3f(V[p1],V[p1+1],V[p1+2]);
			glTexCoord2f(VT[t2],VT[t2+1]); glVertex3f(V[p2],V[p2+1],V[p2+2]);
			glTexCoord2f(VT[t3],VT[t3+1]); glVertex3f(V[p3],V[p3+1],V[p3+2]);
			//remove sets already drawn
			F.erase(F.begin());
			F.erase(F.begin());
			F.erase(F.begin());
			F1.erase(F1.begin());
			F1.erase(F1.begin());
			F1.erase(F1.begin());
		};
		glEnd();
	}

	glFlush();
	glutSwapBuffers();
	yRotationAngle += 5; // Increment our rotation value  
  
	if (yRotationAngle > 360.0f) // If we have rotated beyond 360 degrees (a full rotation)  
		yRotationAngle -= 360.0f;
}

/* checks for joystick input then draws */
GLvoid IdleGLScene(){
	PollJoyStick();
	DrawGLScene();
}

/* this gets called everytime your window resizes or moves */
GLvoid ReSizeGLScene(int width, int height){
	if(height == 0)
		height = 1;
	glViewport(0,0,width,height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	if(stereo)
		gluPerspective(45.0f, (GLfloat)width/((GLfloat)height * 2.0f), 0.1f, 2000.0f);
	else
		gluPerspective(45.0f, (GLfloat)width/(GLfloat)height, 0.1f, 2000.0f);

	windowWidth = width;
	windowHeight = height;
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

GLvoid GLKeyDown(unsigned char key, int x, int y){
	// if "esc" is pressed close
	if(key == KEYBOARD_ESC)
		exit(0);
	// if "f" key is pressed go into fullscreen mode
	if(key == KEYBOARD_F){
		if(stereo)
			return;
		if(fullscreen){
			fullscreen = 0;
			glutReshapeWindow(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);
			glutPositionWindow(100,100);
		}
		else{
			glutFullScreen();
			fullscreen = 1;
		}
	}
	/*
	 * remove this section on stereo if you are not windows
	 *
	 */
	// if "s" key is pressed go into stereo mode
	if(key == KEYBOARD_S){ 
		if(stereo){
			if(!fullscreen){
				glutReshapeWindow(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);
				glutPositionWindow(100,100);
			}
			else{
				glutFullScreen();
			}
			stereo = 0;
			DrawGLScene();
		}
		else{
			glutFullScreen();
			stereo = 1;
			DrawGLScene();
		}
	}
}

/* remove this if you are not running on windows. Other operating systems will need other options for loading bitmaps */
bool NeHeLoadBitmap(LPTSTR szFileName, GLuint &texid)					// Creates Texture From A Bitmap File
{
	HBITMAP hBMP;														// Handle Of The Bitmap
	BITMAP	BMP;														// Bitmap Structure
	glGenTextures(1, &texid);											// Create The Texture
	hBMP=(HBITMAP)LoadImage(GetModuleHandle(NULL), szFileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE );
	if (!hBMP)															// Does The Bitmap Exist?
		return FALSE;													// If Not Return False
	GetObject(hBMP, sizeof(BMP), &BMP);									// Get The Object
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);								// Pixel Storage Mode (Word Alignment / 4 Bytes)
	glBindTexture(GL_TEXTURE_2D, texid);								// Bind To The Texture ID
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);	// Linear Min Filter
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	// Linear Mag Filter
	glTexImage2D(GL_TEXTURE_2D, 0, 3, BMP.bmWidth, BMP.bmHeight, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits);
	DeleteObject(hBMP);													// Delete The Object
	return TRUE;														// Loading Was Successful
}

/* some joystick example code */
GLvoid PollJoyStick(){
	JOYINFOEX stuff;
	ZeroMemory(&stuff, sizeof(stuff));
	stuff.dwSize = sizeof(stuff);
	stuff.dwFlags |= JOY_RETURNALL;
	joyGetPosEx(JOYSTICKID1, &stuff);

	/* axis forcing them between 0 and 1 you need to check for dead zones */
	float axisY = (float)(stuff.dwYpos - 32767.0f) / 32768;
	float axisX = (float)(stuff.dwXpos - 32767.0f) / 32768;
	float axisZ = (float)(stuff.dwZpos - 32767.0f) / 32768;
	float axisR = (float)(stuff.dwRpos - 32767.0f) / 32768;
	/* 
	 * the buttons are set up on powers of 2
	 * i.e. button 1 = 1 or (2 ^ (button - 1)) or (2 ^ 0)
	 * button 2 = 2 or (2 ^ (2 - 1)) or (2 ^ 1)
	 * button 3 = 4 or (2 ^ (3 - 1)) or (2 ^ 2)
	 * etc...
	 * You can use flags to test them
	 * e.g. (buttons & 0x00000001) or (buttons & 1) would give me true if 1 is pressed and false other wise or
	 * (buttons & 0x00000100) or (buttons & 4) would give me true if button 3 is pressed
	 * This allows the same int to pack multiple button pressed into one unsigned int
	 * so buttons would be 3 if buttons 1 and 2 were pressed. You can do (buttons & 1) would give true
	 * and (buttons & 2) would give true but (buttons & 8) would give false;
	 */
	unsigned int buttons = stuff.dwButtons;

	/*std::cout << "My joystick outputs: " << std::endl
		<< "Axis Data: " << std::endl
		<< "\tAxisY: " << axisY << std::endl
		<< "\tAxisX: " << axisX << std::endl
		<< "\tAxisZ: " << axisZ << std::endl
		<< "\tAxisR: " << axisR << std::endl << std::endl
		<< "Button Data:" << std::endl
		<< "\tMy Button Data is: " << buttons << std::endl;*/
}